package com.wechat.file.service;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.MutableClassToInstanceMap;
import com.wechat.common.web.exception.BusinessException;
import com.wechat.common.web.result.ResultCode;
import com.wechat.file.constant.RedisKey;
import com.wechat.file.entity.FileInfo;
import com.wechat.file.mapper.FileInfoMapper;
import com.wechat.file.request.CheckFileRequest;
import com.wechat.file.request.UploadRequest;
import com.wechat.file.response.CheckFileResponse;
import com.wechat.file.response.UploadResponse;
import com.wechat.file.util.FileUtil;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Service
@Slf4j
public class UploadService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private FileInfoMapper fileInfoMapper;

    public CheckFileResponse check(CheckFileRequest request) {
        int totalChunks = calculateTotalChunks(request.getFileSize(), request.getChunkSize());
        // 1. 检查文件是否上传
        Map<String, String> fileHash = getFileHashFromReds(request.getFileMd5());
        if (fileHash != null && !fileHash.isEmpty()) {
            // 秒传
            return CheckFileResponse.buildExistResponse(fileHash.get("fileId"), fileHash.get("downloadUrl"));
        }
        FileInfo existFile = getFromDBByMd5(request.getFileMd5());
        if (existFile != null) {
            // 文件存在于数据库，重新缓存到Redis
            cacheFileHash(request.getFileMd5(), existFile.getFileId(), existFile.getFileUrl());
            return CheckFileResponse.buildExistResponse(existFile.getFileId(), existFile.getFileUrl());
        }
        // 2. 检查断点续传（检查已上传的分片）
        Set<Integer> uploadedChunks = getUploadedChunks(request.getFileMd5());
        if (uploadedChunks != null) {
            // 断点续传：计算缺失的分片
            Set<Integer> missingChunks = calculateMissingChunks(uploadedChunks, totalChunks);
            return CheckFileResponse.buildPartialResponse(missingChunks, totalChunks);
        }
        // 3. 初次上传
        return CheckFileResponse.buildInitUploadResponse();
    }

    public UploadResponse upload(UploadRequest request, MultipartFile file) {
        if (!checkFile(file)) {
            throw new BusinessException(ResultCode.FILE_UPLOAD_FAILED);
        }
        // 检查文件是否上传
        Map<String, String> fileHash = getFileHashFromReds(request.getFileMd5());
        if (fileHash != null && !fileHash.isEmpty()) {
            // 秒传
            return UploadResponse.buildExistResponse(fileHash.get("fileId"), fileHash.get("downloadUrl"));
        }
        // 上传文件

    }

    private boolean checkFile(MultipartFile file) {
        // 文件大小小于50MB
        if (file.getSize() > 52428800) {
            return false;
        }
        if (file.getSize() <= 0) {
            return false;
        }
        return FileUtil.isValidFileType(file);
    }

    private Set<Integer> calculateMissingChunks(Set<Integer> uploadedChunks, int totalChunks) {
        return IntStream.range(0, totalChunks)
                .filter(i -> !uploadedChunks.contains(i))
                .boxed()
                .collect(Collectors.toSet());
    }

    private int calculateTotalChunks(Long fileSize, Long chunkSize) {
        return (int) Math.ceil((double) fileSize / chunkSize);
    }

    private Set<Integer> getUploadedChunks(String fileMd5) {
        String key = RedisKey.UPLOAD_INFO + fileMd5;
        Set<String> chunks = stringRedisTemplate.opsForSet().members(key);
        if (chunks == null || chunks.isEmpty()) {
            return null;
        }
        return chunks.stream().map(Integer::parseInt).collect(Collectors.toSet());
    }

    private FileInfo getFromDBByMd5(String md5) {
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getFileMd5, md5);
        return fileInfoMapper.selectOne(queryWrapper);
    }

    private Map<String, String> getFileHashFromReds(String md5) {
        String redisKey = RedisKey.FILE_HASH + md5;
        return stringRedisTemplate.opsForHash().entries(redisKey).entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> entry.getKey().toString(),
                        entry -> entry.getValue().toString()
                ));
    }

    private void cacheFileHash(String md5, String fileId, String fileUrl) {
        String redisKey = RedisKey.FILE_HASH + md5;
        Map<String, String> hash = new HashMap<>(2);
        hash.put("fileId", fileId);
        hash.put("downloadUrl", fileUrl);
        stringRedisTemplate.opsForHash().putAll(redisKey, hash);
        stringRedisTemplate.expire(redisKey, Duration.ofDays(30));
    }

}
